/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package modbuspal.binding;

import java.io.IOException;
import java.io.OutputStream;
import modbuspal.automation.Automation;
import modbuspal.automation.AutomationExecutionListener;
import modbuspal.instanciator.Instantiable;
import modbuspal.slave.ModbusRegisters;

/**
 * Defines a binding
 * @author nnovic
 */
public abstract class Binding
implements AutomationExecutionListener, Cloneable, Instantiable<Binding>
{

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    private Automation automation;
    private int order;
    private ModbusRegisters registers;
    private int registerAddress;

    /** creates a new instance. */
    public Binding()
    {
    }

    @Override
    public void init()
    {
    }

    @Override
    public void reset()
    {
        throw new RuntimeException("This method is implemented because of the Instantiable interface, but is never used");
    }

    /**
     *  This method is called by ModbusRegisters. It should not be called
     *  directly. Its purpose is to define which register is the target
     *  of the binding.
     * @param l reference on the modbus registers to look into
     * @param address address of the register that is the target of the binding
     */
    public final void attach(ModbusRegisters l, int address)
    {
        registers = l;
        registerAddress = address;
        automation.addAutomationExecutionListener(this);
    }


    /**
     *  This method is called by ModbusRegisters. It should not be called
     *  directly. Its purpose is to undefine which register is the target
     *  of the binding.
     */
    public final void detach()
    {
        registers=null;
        automation.removeAutomationExecutionListener(this);
    }

    @Override
    public void automationValueHasChanged(Automation source, double time, double value)
    {
        registers.notifyRegisterChanged(registerAddress);
    }

    @Override
    public void automationHasStarted(Automation source) {
    }

    @Override
    public void automationHasEnded(Automation source) {
    }

    @Override
    public void automationReloaded(Automation source) {
    }

    /**
     *
     * @return size in bits
     */
    public abstract int getSize();

    /**
     * saves the configuration of the binding.
     * @param out outpustream to write into
     * @throws IOException
     */
    public final void save(OutputStream out)
    throws IOException
    {
        StringBuilder tag = new StringBuilder("<binding");
        tag.append(" automation=\"").append(automation.getName()).append("\"");
        tag.append(" class=\"").append(getClassName()).append("\"");
        tag.append(" order=\"").append(String.valueOf(order)).append("\"");
        tag.append("/>\r\n");
        out.write( tag.toString().getBytes() );
    }

    /**
     *  This method is called by ModbusPalProject and ModbusRegistersPanel.
     *  It should not be called directly. Its purpose is to define which
     *  automation is the source of the binding.
     * @param source reference on the source automation
     * @param order the word order for the value generated by the automation
     */
    public final void setup(Automation source, int order)
    {
        automation = source;
        this.order = order;
        if( this.order < 0 )
        {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Calls getRegister(int,double) with the order and value of the automation
     * previously specified by setup(Automation,int)
     * @return the value of the register
     */
    public final int getRegister()
    {
        return getRegister(order, automation.getCurrentValue());
    }


    /**
     * Process the specified value and return the 16-word register corresponding
     * to the requested order. 
     * @param order the rank of the 16-word register to return, depending of the
     * mapping implemented by this binding.
     * @param value the value to process
     * @return the register of the specified order, after processing of the input value
     */
    public abstract int getRegister(int order, double value);


    /**
     * Calls getCoil(int,double) with the order and value of the automation
     * previously specified by setup(Automation,int)
     * @return the value of the register
     */
    public final boolean getCoil()
    {
        return getCoil(order, automation.getCurrentValue());
    }


    /**
     * Process the specified value and return the boolean value (coil)  corresponding
     * to the requested order. 
     * @param order the rank of the boolean (coil) to return, depending of the
     * mapping implemented by this binding.
     * @param value the value to process
     * @return the boolean (coil) of the specified order, after processing of the input value
     */
    public boolean getCoil(int order, double value)
    {
        int rankWord = order / 16;
        int rankBit = order % 16;
        int reg = getRegister(rankWord,value);
        int mask = 1 << rankBit;
        return (reg&mask)!=0;
    }

    @Override
    public String toString()
    {
        return automation.getName() + " (" + getClassName() + ":" + String.valueOf(order) + ")";
    }

    /**
     * Returns the name of the automation that 
     * has been associated to this binding by
     * calling the setup() method.
     * @return the name of the automation
     */
    public String getAutomationName()
    {
        return automation.getName();
    }

    @Override
    public String getClassName()
    {
        return getClass().getSimpleName();
    }

    @Override
    public Binding newInstance()
    throws InstantiationException, IllegalAccessException
    {
        return getClass().newInstance();
    }


}
